package com.leizi.igym.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Highlight;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.leizi.igym.BaseContext.UserThreadLocal;
import com.leizi.igym.constant.PostConstant;
import com.leizi.igym.dao.PostESDao;
import com.leizi.igym.domain.Post;
import com.leizi.igym.domain.User;
import com.leizi.igym.dto.*;
import com.leizi.igym.enums.ReturnCodeEnum;
import com.leizi.igym.exception.BusinessException;
import com.leizi.igym.mapper.PostMapper;
import com.leizi.igym.pojo.ContextUser;
import com.leizi.igym.service.PostService;
import com.leizi.igym.service.UserService;
import com.leizi.igym.util.RedisUtils;
import com.leizi.igym.vo.PostVO;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

;


/**
 * @author PC
 * @description 针对表【t_post】的数据库操作Service实现
 * @createDate 2024-04-29 16:24:50
 */
@Service
public class PostServiceImpl extends ServiceImpl<PostMapper, Post>
        implements PostService {

    @Resource
    private UserService userService;

    @Resource
    private ElasticsearchClient esClient;

    @Resource
    private PostESDao postESDao;

    @Override
    public PostVO getByIdWithUserInfo(Long id) {
        if (Objects.isNull(id) || id <= 0) {
            throw new BusinessException(ReturnCodeEnum.PARAMS_ERROR);
        }

        PostVO postVO = this.getBaseMapper().selectByIdWithUserInfo(id);

        if (Objects.isNull(postVO)) {
            throw new BusinessException(ReturnCodeEnum.NOT_FOUND_ERROR, "该条帖子不存在");
        }
        ContextUser contextUser = UserThreadLocal.get();
        if (Objects.isNull(contextUser)) {
            throw new BusinessException(ReturnCodeEnum.LOGIN_STATUS_ERROR);
        }
        // 初始化是否点赞和收藏
        this.loadLikeOrCollected(postVO, contextUser.getId());
        return postVO;
    }


    @Override
    @Transactional
    public boolean add(PostAddDTO dto) {
        if (Objects.isNull(dto)) {
            throw new BusinessException(ReturnCodeEnum.PARAMS_ERROR);
        }

        Long userId = dto.getUserId();
        String title = dto.getTitle();
        String pictures = dto.getPictures();
        String content = dto.getContent();

        User dbUser = userService.getById(userId);
        if (Objects.isNull(dbUser)) {
            throw new BusinessException(ReturnCodeEnum.SAVE_ERROR, "发布者信息异常!");
        }

        if (StringUtils.isAnyBlank(title, content)) {
            throw new BusinessException(ReturnCodeEnum.PARAMS_ERROR);
        }

        Post post = new Post();
        BeanUtils.copyProperties(dto, post);
        int insert = this.getBaseMapper().insert(post);

        if (insert < 1) {
            throw new BusinessException(ReturnCodeEnum.SAVE_ERROR, "新增帖子失败！");
        }

        PostESDTO postESDTO = PostESDTO.builder()
                .id(post.getId())
                .userId(userId)
                .pictures(pictures)
                .title(title)
                .content(content)
                .nickname(dbUser.getNickname())
                .createTime(post.getCreateTime())
                .avatar(dbUser.getAvatar())
                .likeNum(post.getLikeNum())
                .collectNum(post.getCollectNum())
                .build();

        postESDao.save(postESDTO);

        return true;
    }

    @Override
    @Transactional
    public boolean updateAndToES(PostDTO dto) {
        if (Objects.isNull(dto)) {
            throw new BusinessException(ReturnCodeEnum.PARAMS_ERROR);
        }
        String content = dto.getContent();
        String pictures = dto.getPictures();
        String title = dto.getTitle();
        Long id = dto.getId();

        if (StringUtils.isAnyBlank(content, title)) {
            throw new BusinessException(ReturnCodeEnum.PARAMS_ERROR);
        }

        Post dbPost = this.getBaseMapper().selectById(id);
        if (Objects.isNull(dbPost)) {
            throw new BusinessException(ReturnCodeEnum.UPDATE_ERROR, "该条帖子修改异常！");
        }

        UpdateWrapper<Post> uw = new UpdateWrapper<>();
        uw.eq("id", id);
        uw.set("content", content);
        uw.set("title", title);
        uw.set("pictures", pictures);

        int update = this.getBaseMapper().update(uw);
        if (update < 1) {
            throw new BusinessException(ReturnCodeEnum.UPDATE_ERROR, "帖子修改失败！");
        }

        // 在此次更新es时，我采取先获取在更新，
        Optional<PostESDTO> optional = postESDao.findById(id);
        if (optional.isEmpty()) {
            throw new BusinessException(ReturnCodeEnum.UPDATE_ERROR, "该条帖子修改异常！");
        }

        // 赋新值
        PostESDTO postESDTO = optional.get();
        postESDTO.setContent(content);
        postESDTO.setTitle(title);
        postESDTO.setPictures(pictures);

        // 保存
        postESDao.save(postESDTO);
        return true;
    }

    @Override
    public boolean deleteWithES(Long id) {
        if (Objects.isNull(id) || id < 0) {
            throw new BusinessException(ReturnCodeEnum.PARAMS_ERROR);
        }

        Post dbPost = this.getBaseMapper().selectById(id);
        if (Objects.isNull(dbPost)) {
            throw new BusinessException(ReturnCodeEnum.DELETE_ERROR, "该条帖子已不存在！");
        }

        int delete = this.getBaseMapper().deleteById(id);
        if (delete < 1) {
            throw new BusinessException(ReturnCodeEnum.DELETE_ERROR, "删除帖子失败！！");

        }

        postESDao.deleteById(id);
        RedisUtils.remove(PostConstant.POST_COLLECTED_REDIS_PREFIX+id);
        RedisUtils.remove(PostConstant.POST_LIKED_REDIS_PREFIX+id);
        return true;
    }

    @Override
    public List<PostVO> getBySearch(PostSearchDTO dto) throws IOException {
        ContextUser contextUser = UserThreadLocal.get();
        if (Objects.isNull(contextUser)) {
            throw new BusinessException(ReturnCodeEnum.LOGIN_STATUS_ERROR);
        }
        String searchText = dto.getSearchText();
        if (StringUtils.isBlank(searchText)) {
            return this.get(dto.getCurrent(), dto.getPageSize());
        }

        // 构造查询条件
        BoolQuery.Builder boolBuilder = new BoolQuery.Builder();
        BoolQuery boolQuery = boolBuilder
                .should(s -> s
                        .match(m -> m
                                .field("content")
                                .query(searchText)))
                .should(s -> s
                        .match(m -> m
                                .field("title")
                                .query(searchText)))
                .should(s -> s
                        .match(m -> m
                                .field("nickname")
                                .query(searchText))).build();


        // 高亮显示
        Highlight.Builder highlightBuilder = new Highlight.Builder();
        Highlight highlight = highlightBuilder
                .fields("title", hf -> hf
                        .preTags("<span style='color:red;font-weight: bold'>")
                        .postTags("</span>"))
                .fields("content", hf -> hf
                        .preTags("<span style='color:red;font-weight: bold'>")
                        .postTags("</span>"))
                .fields("nickname", hf -> hf
                        .preTags("<span style='color:red;font-weight: bold'>")
                        .postTags("</span>")).build();

        SearchRequest request = SearchRequest.of(res -> res
                .index("post")
                .highlight(highlight)
                .query(q -> q
                        .bool(boolQuery))
                .sort(s -> s
                        .field(f -> f
                                .field("createTime")
                                .order(SortOrder.Desc))));
        SearchResponse<PostESDTO> response = esClient.search(request, PostESDTO.class);

        HitsMetadata<PostESDTO> metadata = response.hits();
        if (Objects.nonNull(metadata.total()) && metadata.total().value() == 0) {
            return Collections.emptyList();
        }

        List<Hit<PostESDTO>> hitList = metadata.hits();
        return hitList.stream().map(hit -> {
            PostVO postVO = new PostVO();
            PostESDTO source = hit.source();
            Map<String, List<String>> map = hit.highlight();
            if (map.containsKey("title")) {
                source.setTitle(map.get("title").get(0));
            }
            if (map.containsKey("content")) {
                source.setContent(map.get("content").get(0));
            }
            if (map.containsKey("nickname")) {
                source.setContent(map.get("nickname").get(0));
            }
            BeanUtils.copyProperties(source, postVO);
            this.loadLikeOrCollected(postVO, contextUser.getId());
            return postVO;
        }).collect(Collectors.toList());

    }

    @Override
    public List<PostVO> get(Long current, Long pageSize) {
        if (ObjectUtils.anyNull(current, pageSize) || current < 0 || pageSize < 0) {
            throw new BusinessException(ReturnCodeEnum.PARAMS_ERROR);
        }
        List<PostVO> postVOS = this.getBaseMapper().selectWithUserInfo(current - 1, pageSize);
        ContextUser contextUser = UserThreadLocal.get();
        if (Objects.isNull(contextUser)) {
            throw new BusinessException(ReturnCodeEnum.LOGIN_STATUS_ERROR);
        }
        postVOS.forEach(postVO -> {
            loadLikeOrCollected(postVO, contextUser.getId());
        });
        return postVOS;
    }

    @Override
    @Transactional
    public boolean doLike(Long id, PostLikeDTO dto) {
        if (ObjectUtils.anyNull(id, dto) || id < 0) {
            throw new BusinessException(ReturnCodeEnum.PARAMS_ERROR);
        }
        Integer liked = dto.getLiked();
        Long userId = dto.getUserId();
        if (Objects.isNull(userService.getById(userId))) {
            throw new BusinessException(ReturnCodeEnum.NOT_FOUND_ERROR, "当前用户异常！");
        }
        if (!PostConstant.LIKE_LIST.contains(liked)) {
            throw new BusinessException(ReturnCodeEnum.PARAMS_ERROR, "帖子点赞相关操作异常！");
        }

        PostVO dbPostVO = this.getBaseMapper().selectByIdWithUserInfo(id);
        if (Objects.isNull(dbPostVO)) {
            throw new BusinessException(ReturnCodeEnum.NOT_FOUND_ERROR, "该条帖子已不存在！");
        }

        if (dbPostVO.getLikeNum().equals(0) && liked.equals(PostConstant.LIKED)) {
            throw new BusinessException(ReturnCodeEnum.UPDATE_ERROR, "帖子点赞相关操作异常!");
        }

        UpdateWrapper<Post> uw = new UpdateWrapper<>();
        uw.eq("id", id);

        // 若传回点赞状态量为1（即当前用户是已点赞该帖子），那么就点赞数-1
        uw.set(liked.equals(PostConstant.LIKED), "like_num", dbPostVO.getLikeNum() - 1);
        uw.set(liked.equals(PostConstant.UNLIKED), "like_num", dbPostVO.getLikeNum() + 1);
        int update = this.getBaseMapper().update(uw);
        if (update < 1) {
            throw new BusinessException(ReturnCodeEnum.PARAMS_ERROR, "帖子点赞相关操作异常！");
        }

        boolean res = liked.equals(PostConstant.LIKED) ?
                RedisUtils.sremove(PostConstant.POST_LIKED_REDIS_PREFIX + id, userId + "") :
                RedisUtils.add(PostConstant.POST_LIKED_REDIS_PREFIX + id, userId + "");
        if (!res) {
            throw new BusinessException(ReturnCodeEnum.PARAMS_ERROR, "帖子点赞相关操作异常！");
        }
        return true;
    }

    @Override
    @Transactional
    public boolean doCollect(Long id, PostCollectDTO dto) {
        if (ObjectUtils.anyNull(id, dto) || id < 0) {
            throw new BusinessException(ReturnCodeEnum.PARAMS_ERROR);
        }
        Integer collected = dto.getCollected();
        Long userId = dto.getUserId();
        if (Objects.isNull(userService.getById(userId))) {
            throw new BusinessException(ReturnCodeEnum.NOT_FOUND_ERROR, "当前用户异常！");
        }
        if (!PostConstant.COLLECT_LIST.contains(collected)) {
            throw new BusinessException(ReturnCodeEnum.PARAMS_ERROR, "帖子收藏相关操作异常！");
        }

        PostVO dbPostVO = this.getBaseMapper().selectByIdWithUserInfo(id);
        if (Objects.isNull(dbPostVO)) {
            throw new BusinessException(ReturnCodeEnum.NOT_FOUND_ERROR, "该条帖子已不存在！");
        }

        if (dbPostVO.getCollectNum().equals(0) && collected.equals(PostConstant.LIKED)) {
            throw new BusinessException(ReturnCodeEnum.UPDATE_ERROR, "帖子收藏相关操作异常!");
        }

        UpdateWrapper<Post> uw = new UpdateWrapper<>();
        uw.eq("id", id);

        // 若传回点赞状态量为1（即当前用户是已点赞该帖子），那么就点赞数-1
        uw.set(collected.equals(PostConstant.COLLECTED), "collect_num", dbPostVO.getCollectNum() - 1);
        uw.set(collected.equals(PostConstant.NOT_COLLECTED), "collect_num", dbPostVO.getCollectNum() + 1);
        int update = this.getBaseMapper().update(uw);
        if (update < 1) {
            throw new BusinessException(ReturnCodeEnum.PARAMS_ERROR, "帖子收藏相关操作异常！");
        }

        boolean res = collected.equals(PostConstant.COLLECTED) ?
                RedisUtils.sremove(PostConstant.POST_COLLECTED_REDIS_PREFIX + id, userId + "") :
                RedisUtils.add(PostConstant.POST_COLLECTED_REDIS_PREFIX + id, userId + "");
        if (!res) {
            throw new BusinessException(ReturnCodeEnum.PARAMS_ERROR, "帖子点赞相关操作异常！");
        }
        return true;
    }

    @Override
    public List<PostVO> getMy(Long userId) {
        if (Objects.isNull(userId) || userId < 0) {
            throw new BusinessException(ReturnCodeEnum.PARAMS_ERROR);
        }
        List<PostVO> postVOList = this.getBaseMapper().selectMyWithUserInfo(userId);
        postVOList.forEach(postVO -> {
            loadLikeOrCollected(postVO, userId);
        });
        return postVOList;
    }

    @Override
    public Integer likeCount(Long userId) {
        Set<String> likeKeySet =
                RedisUtils.keys(PostConstant.POST_LIKED_REDIS_PREFIX + "*").stream().filter(k -> RedisUtils.containValue(k,
                userId + "")).collect(Collectors.toSet());
        return likeKeySet.size();
    }

    @Override
    public Integer collectCount(Long userId) {
        Set<String> collectKeySet =
                RedisUtils.keys(PostConstant.POST_COLLECTED_REDIS_PREFIX + "*").stream().filter(k -> RedisUtils.containValue(k,
                        userId + "")).collect(Collectors.toSet());
        return collectKeySet.size();
    }

    private void loadLikeOrCollected(PostVO postVO, Long userId) {
        postVO.setLiked(PostConstant.UNLIKED);
        postVO.setCollected(PostConstant.NOT_COLLECTED);

        if (RedisUtils.containValue(PostConstant.POST_LIKED_REDIS_PREFIX + postVO.getId(), userId + "")) {
            postVO.setLiked(PostConstant.LIKED);
        }
        if (RedisUtils.containValue(PostConstant.POST_COLLECTED_REDIS_PREFIX + postVO.getId(), userId +
                "")) {
            postVO.setCollected(PostConstant.COLLECTED);
        }
    }

}




